/*
 * $Id: frmCustomiseQIF.java 44 2014-11-17 09:47:09Z eldon_r $
 *
 * Created on 20 March 2007, 01:09
 *
 */
package customqif;

import java.awt.Component;
import java.awt.FileDialog;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Properties;
import javax.swing.JOptionPane;
import javax.swing.table.DefaultTableModel;
import org.netbeans.swing.etable.ETableColumnModel;
// import javax.swing.table.TableModel;

/**
 *
 * @author  Edgeberg <eldon_r@users.sf.net>
 */
public class frmCustomiseQIF extends javax.swing.JFrame {

    int intElements = 0;
    int intElement = 0;
    int intType = 0;
    String strHeader = "";
    String strTypes = "DTMNL";
    int D = strTypes.indexOf('D');
    int T = strTypes.indexOf('T');
    int M = strTypes.indexOf('M');
    int N = strTypes.indexOf('N');
    int L = strTypes.indexOf('L');
    String aryQIF[][] = null;
    Boolean aryKeep[] = null;
    String strNarrationPatterns = "";
    String aryNarrationPatterns[];
    String strPatternFile = "";
    DefaultTableModel tableModelInProgress;
    boolean blnCancelModalDialog = false;
    Boolean blnReversedTransactions = false;
    Boolean blnReformatDate = false;    // If we have sensed that we've got MM/DD/YYYY dates; we want YYYYMMDD.
    Boolean blnDDMM = false;            // If the above is true AND we've found an obvious DD/MM/YYYY formatted date in the file.
                                        // FIXME: This is a limited, inflexible check, and we currently only handle these 3 options.
    String strMatchError = "";

    /** Creates new form frmCustomiseQIF */
    public frmCustomiseQIF() {
        initComponents();
        
        // Make the WindowListener our only way out of this app.:
        setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);

        addWindowListener(new WindowAdapter() {

            @Override
            public void windowClosing(WindowEvent e) {
                doTheCloseThing();
            }
        });

        loadState();
        loadGrid();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {
        bindingGroup = new org.jdesktop.beansbinding.BindingGroup();

        jpmTableContext = new javax.swing.JPopupMenu();
        jmiEdit = new javax.swing.JMenuItem();
        jScrollPane1 = new javax.swing.JScrollPane();
        stringTable = new org.netbeans.swing.etable.ETable();
        btnAdd = new javax.swing.JButton();
        btnEdit = new javax.swing.JButton();
        btnRemove = new javax.swing.JButton();
        btnUp = new javax.swing.JButton();
        btnDown = new javax.swing.JButton();
        btnSave = new javax.swing.JButton();
        btnLoad = new javax.swing.JButton();
        btnExecute = new javax.swing.JButton();
        btnLearn = new javax.swing.JButton();
        btnInputFile = new javax.swing.JButton();
        ctlInputFile = new javax.swing.JTextField();
        btnOutputFile = new javax.swing.JButton();
        ctlOutputFile = new javax.swing.JTextField();
        btnFind = new javax.swing.JButton();
        mb = new javax.swing.JMenuBar();
        mFile = new javax.swing.JMenu();
        miOpen = new javax.swing.JMenuItem();
        miSave = new javax.swing.JMenuItem();
        miSaveAs = new javax.swing.JMenuItem();
        miQuit = new javax.swing.JMenuItem();
        mOptions = new javax.swing.JMenu();
        miNarrationOnly = new javax.swing.JMenuItem();
        miReversedTransactions = new javax.swing.JCheckBoxMenuItem();

        jpmTableContext.setInvoker(stringTable);

        org.jdesktop.beansbinding.Binding binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ, btnEdit, org.jdesktop.beansbinding.ELProperty.create("${action}"), jmiEdit, org.jdesktop.beansbinding.BeanProperty.create("action"));
        bindingGroup.addBinding(binding);
        binding = org.jdesktop.beansbinding.Bindings.createAutoBinding(org.jdesktop.beansbinding.AutoBinding.UpdateStrategy.READ, btnEdit, org.jdesktop.beansbinding.ELProperty.create("${label}"), jmiEdit, org.jdesktop.beansbinding.BeanProperty.create("label"));
        bindingGroup.addBinding(binding);

        jmiEdit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnEditActionPerformed(evt);
            }
        });
        jpmTableContext.add(jmiEdit);

        setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
        setTitle("Customise QIF files downloaded from the bank");

        jScrollPane1.setPreferredSize(new java.awt.Dimension(600, 402));

        stringTable.setModel(new javax.swing.table.DefaultTableModel(
            new Object [][] {

            },
            new String [] {
                "#", "Description Pattern", "Transaction Type Pattern", "Transaction Type Replacement", "Annotation"
            }
        ) {
            Class[] types = new Class [] {
                java.lang.Integer.class, java.lang.String.class, java.lang.String.class, java.lang.String.class, java.lang.String.class
            };
            boolean[] canEdit = new boolean [] {
                false, true, true, true, true
            };

            public Class getColumnClass(int columnIndex) {
                return types [columnIndex];
            }

            public boolean isCellEditable(int rowIndex, int columnIndex) {
                return canEdit [columnIndex];
            }
        });
        stringTable.setComponentPopupMenu(jpmTableContext);
        stringTable.setPopupUsedFromTheCorner(true);
        jScrollPane1.setViewportView(stringTable);
        stringTable.getColumnModel().getColumn(0).setMinWidth(35);
        stringTable.getColumnModel().getColumn(0).setPreferredWidth(35);
        stringTable.getColumnModel().getColumn(0).setMaxWidth(50);
        stringTable.getColumnModel().getColumn(1).setPreferredWidth(300);
        stringTable.getColumnModel().getColumn(4).setPreferredWidth(200);

        btnAdd.setMnemonic('A');
        btnAdd.setText("Add");
        btnAdd.setMaximumSize(new java.awt.Dimension(75, 29));
        btnAdd.setMinimumSize(new java.awt.Dimension(75, 29));
        btnAdd.setPreferredSize(new java.awt.Dimension(75, 29));
        btnAdd.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnAddActionPerformed(evt);
            }
        });

        btnEdit.setMnemonic('E');
        btnEdit.setText("Edit");
        btnEdit.setMaximumSize(new java.awt.Dimension(75, 29));
        btnEdit.setMinimumSize(new java.awt.Dimension(75, 29));
        btnEdit.setPreferredSize(new java.awt.Dimension(75, 29));
        btnEdit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnEditActionPerformed(evt);
            }
        });

        btnRemove.setMnemonic('R');
        btnRemove.setText("Remove");
        btnRemove.setMaximumSize(new java.awt.Dimension(75, 29));
        btnRemove.setMinimumSize(new java.awt.Dimension(75, 29));
        btnRemove.setPreferredSize(new java.awt.Dimension(75, 29));
        btnRemove.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnRemoveActionPerformed(evt);
            }
        });

        btnUp.setMnemonic('U');
        btnUp.setText("Up");
        btnUp.setMaximumSize(new java.awt.Dimension(75, 29));
        btnUp.setMinimumSize(new java.awt.Dimension(75, 29));
        btnUp.setPreferredSize(new java.awt.Dimension(75, 29));
        btnUp.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnUpActionPerformed(evt);
            }
        });

        btnDown.setMnemonic('D');
        btnDown.setText("Down");
        btnDown.setMaximumSize(new java.awt.Dimension(75, 29));
        btnDown.setMinimumSize(new java.awt.Dimension(75, 29));
        btnDown.setPreferredSize(new java.awt.Dimension(75, 29));
        btnDown.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnDownActionPerformed(evt);
            }
        });

        btnSave.setMnemonic('S');
        btnSave.setText("Save");
        btnSave.setToolTipText("Save this setup");
        btnSave.setMaximumSize(new java.awt.Dimension(75, 29));
        btnSave.setMinimumSize(new java.awt.Dimension(75, 29));
        btnSave.setPreferredSize(new java.awt.Dimension(75, 29));
        btnSave.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miSaveActionPerformed(evt);
            }
        });

        btnLoad.setMnemonic('L');
        btnLoad.setText("Load");
        btnLoad.setMaximumSize(new java.awt.Dimension(75, 29));
        btnLoad.setMinimumSize(new java.awt.Dimension(75, 29));
        btnLoad.setPreferredSize(new java.awt.Dimension(75, 29));
        btnLoad.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miOpenActionPerformed(evt);
            }
        });

        btnExecute.setMnemonic('x');
        btnExecute.setText("Execute");
        btnExecute.setToolTipText("Do the operation");
        btnExecute.setMaximumSize(new java.awt.Dimension(75, 29));
        btnExecute.setMinimumSize(new java.awt.Dimension(75, 29));
        btnExecute.setPreferredSize(new java.awt.Dimension(75, 29));
        btnExecute.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnExecuteActionPerformed(evt);
            }
        });

        btnLearn.setMnemonic('N');
        btnLearn.setText("Learn");
        btnLearn.setMaximumSize(new java.awt.Dimension(75, 29));
        btnLearn.setMinimumSize(new java.awt.Dimension(75, 29));
        btnLearn.setPreferredSize(new java.awt.Dimension(75, 29));
        btnLearn.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnLearnActionPerformed(evt);
            }
        });

        btnInputFile.setMnemonic('I');
        btnInputFile.setText("Input File");
        btnInputFile.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnInputFileActionPerformed(evt);
            }
        });

        btnOutputFile.setMnemonic('O');
        btnOutputFile.setText("Output File");
        btnOutputFile.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                btnOutputFileActionPerformed(evt);
            }
        });

        btnFind.setMnemonic('R');
        btnFind.setText("Find");
        btnFind.setDisplayedMnemonicIndex(0);
        btnFind.setMaximumSize(new java.awt.Dimension(75, 29));
        btnFind.setMinimumSize(new java.awt.Dimension(75, 29));
        btnFind.setPreferredSize(new java.awt.Dimension(75, 29));

        mFile.setMnemonic('F');
        mFile.setText("File");

        miOpen.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_O, java.awt.event.InputEvent.CTRL_MASK));
        miOpen.setMnemonic('O');
        miOpen.setText("Open");
        miOpen.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miOpenActionPerformed(evt);
            }
        });
        mFile.add(miOpen);

        miSave.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_S, java.awt.event.InputEvent.CTRL_MASK));
        miSave.setMnemonic('S');
        miSave.setText("Save");
        miSave.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miSaveActionPerformed(evt);
            }
        });
        mFile.add(miSave);

        miSaveAs.setMnemonic('A');
        miSaveAs.setText("Save As...");
        miSaveAs.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miSaveAsActionPerformed(evt);
            }
        });
        mFile.add(miSaveAs);

        miQuit.setAccelerator(javax.swing.KeyStroke.getKeyStroke(java.awt.event.KeyEvent.VK_Q, java.awt.event.InputEvent.CTRL_MASK));
        miQuit.setMnemonic('Q');
        miQuit.setText("Quit");
        miQuit.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miQuitActionPerformed(evt);
            }
        });
        mFile.add(miQuit);

        mb.add(mFile);

        mOptions.setMnemonic('O');
        mOptions.setText("Options");

        miNarrationOnly.setText("Narration Only transactions to avoid combining");
        miNarrationOnly.setToolTipText("Configure a list of patterns that represent narrations that stand by themselves");
        miNarrationOnly.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                miNarrationOnlyActionPerformed(evt);
            }
        });
        mOptions.add(miNarrationOnly);

        miReversedTransactions.setText("Transactions in Reversed Date Order");
        mOptions.add(miReversedTransactions);

        mb.add(mOptions);

        setJMenuBar(mb);

        org.jdesktop.layout.GroupLayout layout = new org.jdesktop.layout.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(layout.createSequentialGroup()
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.TRAILING, false)
                            .add(btnOutputFile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                            .add(btnInputFile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 111, Short.MAX_VALUE))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                            .add(ctlInputFile, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 599, Short.MAX_VALUE)
                            .add(ctlOutputFile)))
                    .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING, false)
                    .add(btnExecute, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 110, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnLearn, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnAdd, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnEdit, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnRemove, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnFind, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnUp, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnDown, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnSave, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .add(org.jdesktop.layout.GroupLayout.TRAILING, btnLoad, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
            .add(layout.createSequentialGroup()
                .addContainerGap()
                .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.LEADING)
                    .add(layout.createSequentialGroup()
                        .add(jScrollPane1, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, 500, Short.MAX_VALUE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                            .add(btnInputFile)
                            .add(ctlInputFile, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE))
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(layout.createParallelGroup(org.jdesktop.layout.GroupLayout.BASELINE)
                            .add(btnOutputFile)
                            .add(ctlOutputFile, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
                    .add(layout.createSequentialGroup()
                        .add(btnAdd, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(btnEdit, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(btnRemove, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .add(6, 6, 6)
                        .add(btnFind, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .add(18, 18, 18)
                        .add(btnUp, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(btnDown, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .add(18, 18, 18)
                        .add(btnSave, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(btnLoad, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED, 210, Short.MAX_VALUE)
                        .add(btnExecute, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(org.jdesktop.layout.LayoutStyle.RELATED)
                        .add(btnLearn, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE, org.jdesktop.layout.GroupLayout.DEFAULT_SIZE, org.jdesktop.layout.GroupLayout.PREFERRED_SIZE)))
                .addContainerGap())
        );

        btnAdd.getAccessibleContext().setAccessibleDescription("Add a row");
        btnEdit.getAccessibleContext().setAccessibleDescription("Edit selected row");
        btnRemove.getAccessibleContext().setAccessibleDescription("Remove rows");
        btnUp.getAccessibleContext().setAccessibleDescription("Move selected rows up");
        btnDown.getAccessibleContext().setAccessibleDescription("Move selected rows down");
        btnSave.getAccessibleContext().setAccessibleDescription("Save grid data");
        btnLoad.getAccessibleContext().setAccessibleDescription("Load grid data");
        btnFind.getAccessibleContext().setAccessibleDescription("Find");

        getAccessibleContext().setAccessibleName("Customise QIF");
        getAccessibleContext().setAccessibleDescription("Customise QIF files downloaded from the bank");

        bindingGroup.bind();

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void btnOutputFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnOutputFileActionPerformed
        openFile(evt.getActionCommand());
    }//GEN-LAST:event_btnOutputFileActionPerformed

    private void btnInputFileActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnInputFileActionPerformed
        openFile(evt.getActionCommand());
    }//GEN-LAST:event_btnInputFileActionPerformed

    private void btnLearnActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnLearnActionPerformed
        doTranslation(true);
    }//GEN-LAST:event_btnLearnActionPerformed

    private void miOpenActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_miOpenActionPerformed
        frmLoadSave fls;
        fls = new frmLoadSave(this, false, System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        fls.setVisible(true);
    }//GEN-LAST:event_miOpenActionPerformed

    private void btnExecuteActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnExecuteActionPerformed
        doTranslation(false);
    }//GEN-LAST:event_btnExecuteActionPerformed

    private void miSaveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_miSaveActionPerformed
        saveGrid();
    }//GEN-LAST:event_miSaveActionPerformed

    private void miQuitActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_miQuitActionPerformed
        doTheCloseThing();
    }//GEN-LAST:event_miQuitActionPerformed

    private void btnRemoveActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnRemoveActionPerformed
        if ((stringTable.getRowCount() > 1)
                && (stringTable.getSelectedRowCount() > 0)) {
            DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
            for (int i=stringTable.getRowCount() + 1; i>0 ; i--) {
                if (stringTable.isRowSelected(i)) {
                    tableModel.removeRow(i);
                }
            }
            stringTable.setModel(tableModel);
            numberGrid();
        }
    }//GEN-LAST:event_btnRemoveActionPerformed

    private void btnAddActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnAddActionPerformed
        DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
        if (stringTable.getSelectedRowCount() > 0) {
            tableModel.insertRow(stringTable.getSelectedRow(), (Object[]) null);
        } else {
            tableModel.addRow(new Object[]{0, "", "", "", ""});
        }
        stringTable.setModel(tableModel);
        numberGrid();
    }//GEN-LAST:event_btnAddActionPerformed

    private void miNarrationOnlyActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_miNarrationOnlyActionPerformed
        new frmNarrationOnly(this, strNarrationPatterns).setVisible(true);
    }//GEN-LAST:event_miNarrationOnlyActionPerformed

    private void btnUpActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnUpActionPerformed
        if (stringTable.getSelectedRowCount() > 0 && ! stringTable.isRowSelected(0)) {
            DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
            for (int i=1 ; i<stringTable.getRowCount() ; i++) {
                if (stringTable.isRowSelected(i)) {
                    tableModel.moveRow(i, i, i - 1);
                }
            }
            stringTable.setModel(tableModel);
            stringTable.changeSelection(stringTable.getSelectedRow() - 1, stringTable.getSelectedColumn(), false, false);
            numberGrid();
        }
    }//GEN-LAST:event_btnUpActionPerformed

    private void btnDownActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnDownActionPerformed
        if (stringTable.getSelectedRowCount() > 0 && ! stringTable.isRowSelected(stringTable.getRowCount() - 1)) {
            DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
            for (int i=stringTable.getRowCount() - 2 ; i>0 ; i--) {
                if (stringTable.isRowSelected(i)) {
                    tableModel.moveRow(i, i, i + 1);
                }
            }
            stringTable.setModel(tableModel);
            stringTable.changeSelection(stringTable.getSelectedRow() + 1, stringTable.getSelectedColumn(), false, false);
            numberGrid();
        }
    }//GEN-LAST:event_btnDownActionPerformed

    /**
     * Event handler for the "File \ Save As" menu item
     * @param evt The event that caused the action to be performed
     */
    private void miSaveAsActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_miSaveAsActionPerformed
        frmLoadSave fls = new frmLoadSave(this, true, System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        fls.setVisible(true);
    }//GEN-LAST:event_miSaveAsActionPerformed

    private void btnEditActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnEditActionPerformed
        if ((stringTable.getRowCount() > 1)
                && (stringTable.getSelectedRowCount() == 1)) {
            dlgEdit de = new dlgEdit(this, true,
                "",
                stringTable.getValueAt(stringTable.getSelectedRow(),1).toString(),
                stringTable.getValueAt(stringTable.getSelectedRow(),2).toString(),
                stringTable.getValueAt(stringTable.getSelectedRow(),3).toString(),
                stringTable.getValueAt(stringTable.getSelectedRow(),4).toString(),
                getAccountList(),
                stringTable.getSelectedRow());
            de.setLocation(getX()+100,getY()+100);
            de.setVisible(true);
        }
    }//GEN-LAST:event_btnEditActionPerformed

    /**
     * The main member, which receives initial program control
     * @param args the command line arguments
     */
    public static void main(String args[]) {
        java.awt.EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new frmCustomiseQIF().setVisible(true);
            }
        });
    }

    /**
     * Function to eliminate nulls (changes a null to an empty string, otherwise returns the input string)
     * @param strString
     * @return a String, which will be "" if strString is null, otherwise = strString
     */
    public String nvl(String strString) {
        if (strString == null) {
            return "";
        } else {
            return strString;
        }
    }

    private String nnvl(String strStringToCompare, String strStringToReturnIfNotNull) {
        if (strStringToCompare == null) {
            return "";
        } else {
            return strStringToReturnIfNotNull;
        }
    }

    /**
     * Show a dialog box indicating a given error condition
     * @param window The parent window of the dialog box
     * @param errorText What error text to show
     * @param dialogTitle The dialog box title text
     */
    public void handleException(Component window, String errorText, String dialogTitle) {
        //if (getOption("HitAuthor", false).equals("Yes")) {
        //    System.err.println("Author email not implemented...");
        //}
        //if (getOption("ErrorsToStdout", false).equalsIgnoreCase("Yes")) {
        System.out.print(dialogTitle + ": " + errorText);
        //}
        JOptionPane.showMessageDialog(window, errorText, dialogTitle, JOptionPane.ERROR_MESSAGE);
    }

    /**
     * Split the tab-delimited string strNarrationPatterns into the string array aryNarrationPatterns
     */
    public void tabStrToArray() {
        if (!strNarrationPatterns.trim().equals("")) {
            aryNarrationPatterns = strNarrationPatterns.split("\t");
        } else {
            aryNarrationPatterns = null;
        }
    }

    public boolean matchTransaction(String strSearchDesc, String strTypeCode,
            String strNarration, String strType, String strDate, String strAmount, String strCheque,
            boolean blnCanErrorDlg) {
        String strMatchDate;
        String strMatchAmount;
        String strMatchCheque;
        boolean blnMatchesDate = true;    // These 3 refer to optional patterns, so start with
        boolean blnMatchesAmount = true;  // the assumption that this part matches
        boolean blnMatchesCheque = true;
        boolean blnMatch = false;
        strMatchError = "";
        if (strSearchDesc.contains("|")) {
            strMatchDate = strSearchDesc.concat("|||||").split("\\|", 8)[1];
            strMatchAmount = strSearchDesc.concat("|||||").split("\\|",8)[2];
            strMatchCheque = strSearchDesc.concat("|||||").split("\\|",8)[3];
            if (!nvl(strMatchDate).equals("")) {
                blnMatchesDate = nvl(strDate).matches(strMatchDate);
            }
            if (!nvl(strMatchAmount).equals("")) {
                blnMatchesAmount = nvl(strAmount).matches(strMatchAmount);
            }
            if (!nvl(strMatchCheque).equals("")) {
                blnMatchesCheque = nvl(strCheque).matches(strMatchCheque);
            }
        }
        try {
            if (strNarration.matches(strSearchDesc)
                && nvl(strType).matches(strTypeCode)
                && blnMatchesDate
                && blnMatchesAmount
                && blnMatchesCheque) {
                blnMatch = true;
                //JOptionPane.showMessageDialog(this, "'" + strLine + "' matches '" + strSearchDesc + "'", "Eureka!", JOptionPane.INFORMATION_MESSAGE);
            }
        } catch (java.util.regex.PatternSyntaxException pe1) {
            strMatchError = pe1.getDescription();
            if (blnCanErrorDlg) {
                JOptionPane.showMessageDialog(this, "'" + strSearchDesc + "' has an error in its pattern syntax.\n" + "Error is: '" + pe1.getDescription() + "'", "ERROR", JOptionPane.ERROR_MESSAGE);
            } else {
                throw pe1;
            }
        }
        return blnMatch;
    }
    
    private void doTranslation(boolean learn) {
        boolean blnCancel = false;
        boolean blnMatch;
        boolean blnMatchesDate = true;
        boolean blnMatchesAmount = true;
        String strLine = "";
        String strDesc = "";
        String strSearchDesc;
        String strTypeCode;
        String strReplaceTypeWith;
        String strInputFile;
        String strMatchDate = "";
        String strMatchAmount = "";

        strInputFile = combineNarrations();
        if (strInputFile == null) {
            return;
        }

        int i;
        int e = 0;
        tableModelInProgress = (DefaultTableModel) stringTable.getModel();
        int rows = tableModelInProgress.getRowCount();

        //status1.setText("Processing...");
        BufferedWriter out = null;
        try {
            if (!learn) {
                // Open output file, write header line
                out = new BufferedWriter(new FileWriter(ctlOutputFile.getText()));
                if (strHeader != null) {
                    out.write(strHeader + "\n");
                }
            }
            try {
                while ((e < intElements) && !blnCancel) {
                    if (aryQIF[e][M] != null && aryKeep[e]) {
                        blnMatch = false;
                        for (i = 0; (!blnMatch) && (i < rows); i++) {
                            strSearchDesc = tableModelInProgress.getValueAt(i, 1).toString();
                            strSearchDesc = strSearchDesc.replaceAll("[(]", "\\[\\(\\]");
                            strSearchDesc = strSearchDesc.replaceAll("[)]", "\\[\\)\\]");
                            strTypeCode = tableModelInProgress.getValueAt(i, 2).toString();
                            strTypeCode = strTypeCode.replaceAll("[(]", "\\[\\(\\]");
                            strTypeCode = strTypeCode.replaceAll("[)]", "\\[\\)\\]");
                            strReplaceTypeWith = tableModelInProgress.getValueAt(i, 3).toString();
                            if (matchTransaction(strSearchDesc, strTypeCode, aryQIF[e][M], aryQIF[e][L], aryQIF[e][D], aryQIF[e][T], aryQIF[e][N], true)) {
                                blnMatch = true;
                                aryQIF[e][L] = strReplaceTypeWith;
                                if (!tableModelInProgress.getValueAt(i, 4).toString().equals("")) {
                                    aryQIF[e][M] = aryQIF[e][M].concat(" - ").concat(tableModelInProgress.getValueAt(i, 4).toString());
                                }
                                //JOptionPane.showMessageDialog(this, "'" + strLine + "' matches '" + strSearchDesc + "'", "Eureka!", JOptionPane.INFORMATION_MESSAGE);
                            }
                        }
                        if (!blnMatch) {
                            if (learn) {
                                dlgEdit de = new dlgEdit(this, true,
                                          nvl(aryQIF[e][M]) + nnvl(aryQIF[e][M], "\n")
                                        + nvl(aryQIF[e][L]) + nnvl(aryQIF[e][L], "\n")
                                        + nvl(aryQIF[e][D]) + nnvl(aryQIF[e][D], "\n")
                                        + nvl(aryQIF[e][T]) + nnvl(aryQIF[e][T], "\n")
                                        + nvl(aryQIF[e][N]) + nnvl(aryQIF[e][N], ""),
                                    aryQIF[e][M],
                                    aryQIF[e][L],
                                    aryQIF[e][L],
                                    "",
                                    getAccountList(),
                                    -1);
                                de.setLocation(getX()+jScrollPane1.getWidth()+22,getY()+btnLoad.getY()+mb.getHeight()+btnLoad.getHeight()+35);
                                de.setVisible(true);
                                blnCancel = blnCancelModalDialog;
                                if (!blnCancel) {
                                    rows++;
                                    aryQIF[e][L] = tableModelInProgress.getValueAt(rows - 1, 3).toString();
                                }
                            }
                        }
                    }
                    if (!learn && aryKeep[e]) {
                        if (aryQIF[e][L] != null) {
                            out.write("L" + aryQIF[e][L] + "\n");
                        }
                        if (aryQIF[e][D] != null) {
                            out.write("D" + aryQIF[e][D] + "\n");
                        }
                        if (aryQIF[e][M] != null) {
                            out.write("M" + aryQIF[e][M] + "\n");
                        }
                        if (aryQIF[e][N] != null) {
                            out.write("N" + aryQIF[e][N] + "\n");
                        }
                        if (aryQIF[e][T] != null) {
                            out.write("T" + aryQIF[e][T] + "\n");
                        }
                        out.write("^\n");
                    }
                    e++;
                }
                //in.close();
            } catch (IOException exTransWrite) {
                handleException(this, "Error while writing " + ctlOutputFile.getText() + ": " + exTransWrite.toString(), "btnExecuteActionPerformed subroutine");
            }
            if (!learn) {
                out.close();
            }
        } catch (IOException exTransWrite2) {
            handleException(this, "Error while writing " + ctlOutputFile.getText() + ": " + exTransWrite2.toString(), "btnExecuteActionPerformed subroutine part 2");
        }
    }

    /**
     * Load the working grid from the specified data file
     */
    public void loadGrid() {
        int r, rows;
        String strLine;
        String[] ary;

        // First see if the .CustomQIF directory exists and create it if not
        File myProgramDir = new File(System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        if (!myProgramDir.mkdirs()) {
            File myStartupFile = new File(myProgramDir, strPatternFile + ".Patterns");
            if (myStartupFile.exists()) {
                // Get a copy of the table structure
                DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
                // Clear table first
                rows = tableModel.getRowCount();
                for (r = (rows - 1); r >= 0; r--) {
                    tableModel.removeRow(r);
                    rows--;
                }
                try {
                    BufferedReader in = new BufferedReader(new FileReader(myStartupFile));
                    r = 1;
                    while ((strLine = in.readLine()) != null) {
                        ary = strLine.concat("\t\t\t\t\t\t").split("\t", 5);    // Making sure we at least get 4 tab-delimited elements
                        tableModel.addRow(new Object[]{r, ary[0], ary[1], ary[2], ary[3]});
                        r++;
                    }
                    in.close();
                    stringTable.setModel(tableModel);
                    this.setTitle("CustomiseQIF: " + strPatternFile);
                } catch (IOException ex) {
                    handleException(this, "Error while reading '" + myStartupFile.getAbsolutePath() + "': " + ex.toString(), "miOpenActionPerformed subroutine");
                }
            }
        }
    }

    public void saveGrid() {
        int r, c, rows, columns;
        
        //Ensure no sorting or filtering is in place:
        ETableColumnModel cm;
        cm = (ETableColumnModel) stringTable.getColumnModel();
        cm.clearSortedColumns();
        stringTable.unsetQuickFilter();

        File myProgramDir = new File(System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        if (!myProgramDir.exists()) {
            myProgramDir.mkdirs();
        }
        if (myProgramDir.exists()) {
            DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
            rows = tableModel.getRowCount();
            columns = tableModel.getColumnCount();
            File myStartupFile = new File(myProgramDir, strPatternFile + ".Patterns");
            try {
                BufferedWriter out = new BufferedWriter(new FileWriter(myStartupFile));
                for (r = 0; r < rows; r++) {
                    for (c = 1; c < columns; c++) {
                        out.write(tableModel.getValueAt(r, c).toString());
                        if (c < (columns - 1)) {
                            out.write("\t");
                        }
                    }
                    out.write("\n");
                }
                out.close();
                this.setTitle("CustomiseQIF: " + strPatternFile);
            } catch (IOException ex) {
                handleException(this, "Error while writing '" + myStartupFile.getAbsolutePath() + "': " + ex.toString(), "miSaveActionPerformed subroutine");
            }
        } else {
            handleException(this, "Unable to create directory '" + myProgramDir.getAbsolutePath() + "'", "miSaveActionPerformed subroutine");
        }
    }

    public void numberGrid() {
        // Sets line numbers in the grid corresponding to the row numbers
        int r, rows;
        rows = stringTable.getRowCount();
        for (r = 0; r < rows; r++) {
            stringTable.setValueAt(r, r, 0);
        }
    }

    private void doTheCloseThing() {
        int selection = JOptionPane.showConfirmDialog(
                null,
                "Are you sure you want to quit?", "CustomiseQIF",
                JOptionPane.YES_NO_CANCEL_OPTION,
                JOptionPane.WARNING_MESSAGE);
        if (selection == JOptionPane.YES_OPTION) {
//            List keys = stringTable.getRowSorter().getSortKeys();
//            List oldKeys = keys;
//            keys.clear();
//            RowSorter.SortKey sk = new RowSorter(0, "ASCENDING");
//            keys.add(RowSorter.SortKey [0, "ASCENDING"]));
            saveGrid();
            saveState();
            dispose();      // Closes the frame
            System.exit(0); // Terminates the application
        }
        // If the user chose "No" or "Cancel" we do nothing
    }

    private void saveState() {
        File myProgramDir = new File(System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        if (!myProgramDir.exists()) {
            myProgramDir.mkdirs();
        }
        if (myProgramDir.exists()) {
            Properties prop = new Properties();
            OutputStream output = null;
            try {
                FileInputStream input = new FileInputStream(myProgramDir.getAbsolutePath() + System.getProperty("file.separator") + "State");
                prop.load(input); // Ensures properties not explicitly handled below are still saved back to the file
            } catch (IOException ioei) {
                // No problem
            }
            try {
                output = new FileOutputStream(myProgramDir.getAbsolutePath() + System.getProperty("file.separator") + "State");
                prop.setProperty("InputFile_" + System.getProperty("os.name").replace(' ', '_'), ctlInputFile.getText());
                prop.setProperty("OutputFile_" + System.getProperty("os.name").replace(' ', '_'), ctlOutputFile.getText());
                prop.setProperty("NarrationOnlyPatterns", strNarrationPatterns);
                prop.setProperty("PatternFile", strPatternFile);
                blnReversedTransactions = miReversedTransactions.getState();
                prop.setProperty("ReversedTransactions", blnReversedTransactions.toString());
                prop.setProperty("Geometry",
                        String.valueOf(this.getLocation().x) + "," +
                        String.valueOf(this.getLocation().y) + "," +
                        String.valueOf(this.getSize().width) + "," +
                        String.valueOf(this.getSize().height));
                prop.store(output, null);
            } catch (IOException ioex) {
                handleException(this, "Error while writing '" + output.toString() + "': " + ioex.toString(), "saveState subroutine");
            } finally {
                if (output != null) {
                    try {
                        output.close();
                    } catch (IOException e) {
                        handleException(this, "Error while closing '" + output.toString() + "': " + e.toString(), "saveState subroutine");
                    }
                }
            }
        } else {
            handleException(this, "Unable to create directory '" + myProgramDir.getAbsolutePath() + "'", "saveState subroutine");
        }
    }

    private void loadState() {
        String strLine;
        int intEqOffset;
        String strKey;
        String strValue;
        String aryGeom[];

        // Set defaults for when values are not set in State file, or there is not yet a State file
        blnReversedTransactions = false;
        ctlInputFile.setText(System.getProperty("user.home") + System.getProperty("file.separator") + "inputfile.qif");
        ctlOutputFile.setText(System.getProperty("user.home") + System.getProperty("file.separator") + "outputfile.qif");

        // First see if the .CustomQIF directory exists and create it if not
        File myProgramDir = new File(System.getProperty("user.home") + System.getProperty("file.separator") + ".CustomQIF");
        FileInputStream input = null;
        if (!myProgramDir.mkdirs()) {
            File myStateFile = new File(myProgramDir, "State");
            if (myStateFile.exists()) {
                Properties prop = new Properties();
                try {
                    input = new FileInputStream(myProgramDir.getAbsolutePath() + System.getProperty("file.separator") + "State");
                    prop.load(input);
                } catch (IOException ioei) {
                    // No problem
                }
                strKey = "InputFile_" + System.getProperty("os.name").replace(' ', '_');
                strValue = prop.getProperty("InputFile_" + System.getProperty("os.name").replace(' ', '_'));
                if (strValue != null) {
                    ctlInputFile.setText(strValue);
                }
                strValue = prop.getProperty("OutputFile_" + System.getProperty("os.name").replace(' ', '_'));
                if (strValue != null) {
                    ctlOutputFile.setText(strValue);
                }
                strPatternFile = prop.getProperty("PatternFile");
                strValue = prop.getProperty("Geometry");
                aryGeom = strValue.concat(",,,").split(",");
                int intX = Integer.valueOf(aryGeom[0]);
                if (intX < 0) {
                    intX = this.getLocation().x;
                }
                int intY = Integer.valueOf(aryGeom[1]);
                if (intY < 0) {
                    intY = this.getLocation().y;
                }
                int intWidth = Integer.valueOf(aryGeom[2]);
                if (intWidth<10) {
                    intWidth=this.getSize().width;
                }
                int intHeight = Integer.valueOf(aryGeom[3]);
                if (intHeight<10) {
                    intHeight=this.getSize().height;
                }
                this.setLocation(intX, intY);
                this.setSize(intWidth, intHeight);
                strNarrationPatterns = prop.getProperty("NarrationOnlyPatterns");
                tabStrToArray();
                blnReversedTransactions = prop.getProperty("ReversedTransactions").equals("true");
                miReversedTransactions.setSelected(blnReversedTransactions);
                if (input != null) {
                    try {
                        input.close();
                    } catch (IOException ex) {
                        handleException(this, "Error while closing '" + myStateFile.getAbsolutePath() + "': " + ex.toString(), "miOpenActionPerformed subroutine");
                    }
                }
            }
        }
    }

    private void openFile(String string) {
        FileDialog dlg = new FileDialog(this);
        if (ctlInputFile.getText().contains(System.getProperty("file.separator"))) {
            dlg.setFile(ctlInputFile.getText().substring(ctlInputFile.getText().lastIndexOf(System.getProperty("file.separator")) + 1));
            dlg.setDirectory(ctlInputFile.getText().substring(0, ctlInputFile.getText().lastIndexOf(System.getProperty("file.separator"))));
        } else {
            dlg.setFile(ctlInputFile.getText());
            dlg.setDirectory("");
        }
        if (string.matches(".*Input.*")) {
            dlg.setMode(FileDialog.LOAD);
        } else {
            dlg.setMode(FileDialog.SAVE);
        }
        dlg.setVisible(true);
        if (dlg.getFile() != null) {
            if (string.matches(".*Input.*")) {
                String infile = dlg.getDirectory() + dlg.getFile();
                ctlInputFile.setText(infile);
                String outfile = infile.replaceAll("[.][Qq][Ii][Ff]", "_mod.qif");
                if (!infile.equalsIgnoreCase(outfile)) {
                    ctlOutputFile.setText(outfile);
                }
            } else {
                ctlOutputFile.setText(dlg.getDirectory() + dlg.getFile());
            }
        }
    }

    private boolean matchesArrayElement(String strToMatch, String aryToSearch[]) {
        int i = 0;
        String strFromArray;
        boolean matchExists = false;
        while (!matchExists && i<aryToSearch.length) {
            strFromArray = aryToSearch[i].replaceAll("[(]", "\\[\\(\\]");
            strFromArray = strFromArray.replaceAll("[)]", "\\[\\)\\]");
            if (strToMatch.matches(strFromArray)) {
                matchExists = true;
            }
            i++;
        }
        return matchExists;
    }

    private String combineNarrations() {
        String strLine, DD, MM, YYYY;
        File temp;
        intElements = 0;
        intElement = 0;
        intType = 0;
        strHeader = "";
        boolean blnDateChecked = false;
        boolean blnDelimiterFound = false;
        String StrMElement;

        try {
            temp = File.createTempFile("CustomQIF", ".tmp");
        } catch (IOException e) {
            handleException(this, "Unable to create intermediate file.", "combineNarrations Function");
            return null;
        }
        temp.deleteOnExit();

        try {
            BufferedReader in1 = new BufferedReader(new FileReader(ctlInputFile.getText()));
            in1.readLine();
            while ((strLine = in1.readLine()) != null) {
                // Handle ^ transaction delimiter either on separate line or at end of a line
                if (strLine.matches("^D[0-9].*") && !blnDateChecked) {
                    if (strLine.matches("^D[0-9]{8}$")) {
                        blnReformatDate = false;
                    } else if (strLine.matches("^D[0-9]{2}/[0-9]{2}/[0-9]{4}$")) {
                        blnReformatDate = true;
                        if (strLine.substring(1, 3).compareTo("12") > 0) {
                            // If "month" > december, it must be "day"
                            blnDDMM = true;
                        }
                        if (strLine.substring(4, 6).compareTo("12") > 0) {
                            blnDDMM = false;
                        }
                    }
                }
                if (strLine.endsWith("^")) {
                    intElements++;
                }
            }
            in1.close();
        } catch (IOException f) {
            handleException(this, "Unable to read input file \"" + ctlInputFile.getText() + "\" to count the elements.", "combineNarrations Function");
            return null;
        }

        aryQIF = new String[intElements][5];
        aryKeep = new Boolean[intElements];

        try {
            BufferedReader in2 = new BufferedReader(new FileReader(ctlInputFile.getText()));
            strLine = in2.readLine();
            strHeader = strLine;
            while ((strLine = in2.readLine()) != null) {
                // Handle ^ transaction delimiter either on separate line or at end of a line
                if (!strLine.equals("^") && strLine.length() > 0) {
                    if (strLine.endsWith("^")) {
                        blnDelimiterFound = true;
                        strLine = strLine.substring(0, strLine.length() - 1);
                    }
                    intType = strTypes.indexOf(strLine.charAt(0));
                    if (intType >= 0) {
                        try {
                            if (intType == D && blnReformatDate) {
                                if (blnDDMM) {
                                    DD = strLine.substring(1,3);
                                    MM = strLine.substring(4,6);
                                } else {
                                    DD = strLine.substring(4,6);
                                    MM = strLine.substring(1,3);
                                }
                                YYYY = strLine.substring(7,11);
                                strLine = strLine.substring(0,1).concat(YYYY).concat(MM).concat(DD);
                            }
                            aryQIF[intElement][intType] = strLine.substring(1);
                        } catch (ArrayIndexOutOfBoundsException ae) {
                            handleException(this, "Error while storing array element: " + ae.toString(), "combineNarrations function");
                        }
                    }
                    if (blnDelimiterFound) {
                        strLine = "^";
                        blnDelimiterFound = false;
                    }
                }
                if (strLine.equals("^")) {
                    aryKeep[intElement] = true;
                    // Fix missing L tags
                    if (aryQIF[intElement][L] == null) {
                        StrMElement = aryQIF[intElement][M].toUpperCase();
                        if (aryQIF[intElement][N] != null
                                || StrMElement.indexOf("PERSONAL CHEQUE") >= 0) {
                            aryQIF[intElement][L] = "CHECK";
                        } else if ((StrMElement.indexOf("CREDIT ") == 0 &&
                                   !StrMElement.startsWith("JOURNAL")) ||
                                (  StrMElement.indexOf("DEPOSIT") >= 0
                                && aryQIF[intElement][T].charAt(0) != '-')) {
                            aryQIF[intElement][L] = "DEP";
                        } else if (StrMElement.indexOf("PERIODIC PAY") >= 0
                                && aryQIF[intElement][T].charAt(0) == '-') {
                            aryQIF[intElement][L] = "REPEATPMT";
                        } else if (StrMElement.indexOf("T'FER") >= 0
                                || StrMElement.indexOf("TFR") >= 0
                                || StrMElement.indexOf("DEBIT TRANSFER") >= 0) {
                            aryQIF[intElement][L] = "XFER";
                        } else if (StrMElement.indexOf("WITHDRAWAL") >= 0) {
                            aryQIF[intElement][L] = "DEBIT";
                        } else if (aryQIF[intElement][T].charAt(0) == '-'
                                || StrMElement.indexOf("RETAIL PURCHASE") >= 0
                                || StrMElement.indexOf("BPAY") >= 0
                                || StrMElement.indexOf("DIRECT DEBIT") >= 0
                                || StrMElement.indexOf("TRANSACTION FEE") >= 0
                                || StrMElement.indexOf("LOAN INTEREST") >= 0
                                || StrMElement.indexOf("BILL PAYMENT") >= 0) {
                            aryQIF[intElement][L] = "PAYMENT";
                        } else if (!aryQIF[intElement][T].equals("0")) {
                            aryQIF[intElement][L] = "DEP";
                        } else if (aryQIF[intElement][T].equals("0")) {
                            aryQIF[intElement][L] = "INFO";
                        }
                    }
                    intElement++;
                }
            }
            in2.close();
        } catch (IOException g) {
            handleException(this, "Unable to read input file \"" + ctlInputFile.getText() + "\".", "combineNarrations Function");
            return null;
        }

        if (intElement > 0) {
            blnReversedTransactions = miReversedTransactions.getState();
            if (blnReversedTransactions) {
                reverseItems();
            }
            try {
                BufferedWriter out = new BufferedWriter(new FileWriter(temp));
                out.write(strHeader);
                for (int i = 0; i < intElements; i++) {
                    if (aryKeep[i]) {
                        // See if there is a narration to add from one transaction below
                        if ((i + 1) < intElements) {
                            if (aryQIF[i][M].equals("LOAN INTEREST")) {
                                // These ones (almost?) always have a 2nd narration of a known pattern after them, either immediately or a bit later:
                                boolean blnLIdone = false;
                                for (int li = 1 ; (i + li < intElements) && (li < 16) && !blnLIdone ; li++) {
                                    if (aryKeep[i + li] && aryQIF[i + li][M].matches("PAYMENT ALTERED .*")) {
                                        aryQIF[i][M] = aryQIF[i][M] + " / " + aryQIF[i + li][M];
                                        aryKeep[i + li] = false;
                                        blnLIdone = true;
                                    }
                                }
                            } else if (!aryQIF[i][T].equals("0") && aryQIF[i + 1][T].equals("0") && !matchesArrayElement(aryQIF[i + 1][M], aryNarrationPatterns)) {
                                aryQIF[i][M] = aryQIF[i][M] + " / " + aryQIF[i + 1][M];
                                aryKeep[i + 1] = false;
                                // Sometimes there will be additional narrations below
                                if ((i + 2) < intElements) {
                                    // Only if same date as first extra narration
                                    if (aryQIF[i + 2][D].equals(aryQIF[i + 1][D]) && aryQIF[i + 2][T].equals("0") && !matchesArrayElement(aryQIF[i + 2][M], aryNarrationPatterns)) {
                                        aryQIF[i][M] = aryQIF[i][M] + " / " + aryQIF[i + 2][M];
                                        aryKeep[i + 2] = false;
                                        if ((i + 3) < intElements) {
                                            // Only if same date as first extra narration
                                            if (aryQIF[i + 3][D].equals(aryQIF[i + 1][D]) && aryQIF[i + 3][T].equals("0") && !matchesArrayElement(aryQIF[i + 3][M], aryNarrationPatterns)) {
                                                aryQIF[i][M] = aryQIF[i][M] + " / " + aryQIF[i + 3][M];
                                                aryKeep[i + 3] = false;
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        for (int j = 0; j < 5; j++) {
                            if (aryQIF[i][j] != null) {
                                out.write(strTypes.charAt(j) + aryQIF[i][j] + "\n");
                            }
                        }
                        out.write("^\n");
                    }
                }
                out.close();
            } catch (IOException ex) {
                handleException(this, "Error while writing intermediate file '" + temp.getAbsolutePath() + "': " + ex.toString(), "combineNarrations function");
            }

        } else {
            handleException(this, "Input file \"" + ctlInputFile.getText() + "\" is either not a .QIF file or is empty..", "combineNarrations Function");
            return null;
        }

        return temp.getAbsolutePath();
    }

    public SortedComboBoxModel getAccountList() {
        int r, rows;
        SortedComboBoxModel itemList = new SortedComboBoxModel();
        DefaultTableModel tableModel = (DefaultTableModel) stringTable.getModel();
        rows = tableModel.getRowCount();
        
        for (r = 0; r < rows; r++) {
            if (itemList.getIndexOf(tableModel.getValueAt(r, 3)) < 0) {
                itemList.addElement(tableModel.getValueAt(r, 3));
            }
        }
        return itemList;
    }

    public void replaceRow(int row, String narration, String xtype, String account, String annotation) {
        stringTable.setValueAt(row, row, 0);    // Row number column
        stringTable.setValueAt(narration, row, 1);
        stringTable.setValueAt(xtype, row, 2);
        stringTable.setValueAt(account, row, 3);
        stringTable.setValueAt(annotation, row, 4);
    }
    
    public void reverseItems() {
        String strTemp;
        boolean blnTemp;
        int i = 0, j = aryQIF.length - 1, k;
        while (i < j) {
            for (k = 0; k < 5; k++) {
                strTemp = aryQIF[i][k];
                aryQIF[i][k] = aryQIF[j][k];
                aryQIF[j][k] = strTemp;
            }
            blnTemp = aryKeep[i];
            aryKeep[i] = aryKeep[j];
            aryKeep[j] = blnTemp;
            i++;
            j--;
        }
    }

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton btnAdd;
    private javax.swing.JButton btnDown;
    private javax.swing.JButton btnEdit;
    private javax.swing.JButton btnExecute;
    private javax.swing.JButton btnFind;
    private javax.swing.JButton btnInputFile;
    private javax.swing.JButton btnLearn;
    private javax.swing.JButton btnLoad;
    private javax.swing.JButton btnOutputFile;
    private javax.swing.JButton btnRemove;
    private javax.swing.JButton btnSave;
    private javax.swing.JButton btnUp;
    private javax.swing.JTextField ctlInputFile;
    private javax.swing.JTextField ctlOutputFile;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JMenuItem jmiEdit;
    private javax.swing.JPopupMenu jpmTableContext;
    private javax.swing.JMenu mFile;
    private javax.swing.JMenu mOptions;
    private javax.swing.JMenuBar mb;
    private javax.swing.JMenuItem miNarrationOnly;
    private javax.swing.JMenuItem miOpen;
    private javax.swing.JMenuItem miQuit;
    private javax.swing.JCheckBoxMenuItem miReversedTransactions;
    private javax.swing.JMenuItem miSave;
    private javax.swing.JMenuItem miSaveAs;
    private org.netbeans.swing.etable.ETable stringTable;
    private org.jdesktop.beansbinding.BindingGroup bindingGroup;
    // End of variables declaration//GEN-END:variables
}
